/***
 * @Description:
 * @Author: LI Yihong
 * @Email: liyh@jihua.ac.cn
 * @Date: 2024-10-23 20:21:02
 * @LastEditTime: 2024-10-23 20:21:03
 * @LastEditors: LI Yihong
 * @FilePath: \STM32F407_Board\applications\I2C\MCP4728.hpp
 */
#pragma once
#include "I2C.hpp"
#include <string>
#include <map>
#include <cstdint>
#include <drivers/pin.h>
#include <cxx_queue.h>
#include <cxx_semaphore.h>
#include <cxx_mail.h>
#include <rtdevice.h>

using std::string;

static constexpr rt_base_t RDY_BSY = GET_PIN(B, 6);
static constexpr rt_base_t LDAC = GET_PIN(B, 7);
// command
static constexpr rt_uint8_t MCP_CMD_RESET = 0x06;        // 复位
static constexpr rt_uint8_t MCP_CMD_WAKEUP = 0x09;       // 唤醒
static constexpr rt_uint8_t MCP_CMD_SOFT_UPDATE = 0x08;  // 软件更新I2C总线值到寄存器
static constexpr rt_uint8_t MCP_CMD_READ_ADDRESS = 0x0c; // 读取存储在EERPROM的设备地址
static constexpr rt_uint8_t MCP_CMD_SET_ADDRESS = 0x60;  // 向EEPROM写入设备地址
static constexpr rt_uint8_t MCP_CMD_SET_REF_VOL = 0x80;  // 设置参考电压
static constexpr rt_uint8_t MCP_CMD_SET_GAIN = 0xC0;     // 设置增益
static constexpr rt_uint8_t MCP_CMD_MULTI_WRITE = 0x40;  // 多通道设置输出电压，不写入EEPROM
static constexpr rt_uint8_t MCP_CMD_SINGLE_WRITE = 0x58; // 单通道设置输出电压，写入EEPROM
// DAC最大分辨率
static constexpr rt_uint16_t MAX_DAC_VAL = 4095;
// DAC外部参考电压
static constexpr float DAC_VDD = 5.0f;
// DAC内部参考电压
static constexpr float DAC_VInternal = 2.048f;
// 从设备地址（7bit，不含W/R bit），出厂默认地址码[A0:A2]=000b，可以通过访问EEPROM配置
static constexpr rt_uint16_t UNI_ADDR = 0x60; /*0b01100000*/
static constexpr rt_uint8_t WRITE_MODE = 0x00;
static constexpr rt_uint8_t READ_MODE = 0X01;

/*-----------enum definition------------*/
enum class V_REF : rt_uint8_t
{
    VDD = 0x00,     /* 5V */
    Internal = 0x01 /* 2.048V */
};

enum class Channel : rt_uint8_t
{
    A = 0x03,
    B = 0x02,
    C = 0x01,
    D = 0x00
};

enum class GAIN : rt_uint8_t
{
    X1 = 0x00,
    X2 = 0x01
};

enum class POWER_DOWN : rt_uint8_t
{
    Normal = 0x00,
    Resister_1k = 0x01,
    Resister_100k = 0x02,
    Resister_500k = 0x03
};

enum class MCP_Status
{
    FAULT,
    OK,
    BUSY,
    READY,
};

enum class MCP4728_CMD
{
    RESET,
    WAKEUP,
    SOFT_UPDATE,
    READ_ADDRESS,
    SET_ADDRESS,
    SET_REF_VOL,
    SET_GAIN,
    SET_POWER_DOWN,
    WRITE_SINGLE_DAC,
    WRITE_FAST
};
/*-----------enum definition------------*/

class MCP4728 : public I2C
{
public:
    struct ChannelInfo
    {
        rt_uint16_t value = 0;           // 0 ~ 4095 (0x0000~ 0x0FFF)
        rt_uint8_t DAC_chnSelect = 0x00; // 0x00->A, 0x01->B, 0x02->C, 0x03->D
        bool por = 0;                    // 1 = Set (Device is powered on with VDD > V_POR), 0 = Powered off state.
        V_REF vRef = V_REF::VDD;
        POWER_DOWN powerDown = POWER_DOWN::Normal;
        GAIN gain = GAIN::X1;
    };

private:
    inline int getPin_LDAC() const { return rt_pin_read(LDAC); }
    inline void setPin_LDAC(bool value) { rt_pin_write(LDAC, value); }
    MCP_Status getPin_RDY_BSY() const;
    MCP_Status writeByte(const rt_uint8_t &byte);
    MCP_Status readByte(rt_uint8_t &byte);
    void cmdProcess(const vector<string> &cmdVec);

    rt_uint8_t address;
    rt_uint8_t vRef;      // (0 0 0 0 Vfa Vfb Vfc Vfd)
    rt_uint8_t gain;      // (0 0 0 0 Ga Gb Gc Gd)
    rt_uint8_t powerDown; // (A1 A0 B1 B0 C1 C0 D1 D0)
    std::map<Channel, ChannelInfo> channelInfo;

public:
    using I2C::I2C;

    void run(void *parameter) override;

    bool init(const rt_uint8_t &addr);
    MCP_Status softUpdate();
    MCP_Status wakeUp();
    MCP_Status config();
    MCP_Status reset();
    MCP_Status readSlaveAddress(rt_uint8_t &EEPROMaddr, rt_uint8_t &inputRegAddr);
    MCP_Status readGeneral(bool flag); // flag: 1->EEPROM, 0->input register
    MCP_Status setSlaveAddress(rt_uint8_t address);
    MCP_Status setRefVoltage(Channel channel, V_REF refType);
    MCP_Status setGain(Channel channel, GAIN gain);
    MCP_Status setPowerDown(Channel channel, POWER_DOWN pd);
    MCP_Status writeSingleDAC(Channel channel, const rt_uint16_t &value);
    MCP_Status writeSingleDACFloat(Channel channel, const float &value);
    MCP_Status writeFast(const rt_uint16_t &valueA, const rt_uint16_t &valueB, const rt_uint16_t &valueC, const rt_uint16_t &valueD);
    MCP_Status writeFastFloat(const float &valueA, const float &valueB, const float &valueC, const float &valueD);

    // communication
    shared_ptr<Mail<vector<string>, 10>> cmdMail;
};
